#include "CameraDevice.h"

using namespace DirectX;

CameraDevice::CameraDevice()
{
  Reset();
}

CameraDevice::~CameraDevice()
{
}

void CameraDevice::Reset()
{
  m_pos = XMFLOAT3(0.f, 10.f, -10.f);
  m_velocity = XMFLOAT3(0.f, 0.f, 0.f);
  m_up = XMFLOAT3(0.0f, 1.0f, 0.0f);

  m_target = XMFLOAT3(0,0,0);

  m_direction = XMFLOAT3(m_target.x - m_pos.x, m_target.y - m_pos.y, m_target.z - m_pos.z);

  CreateProjectionMatrix(XM_PIDIV4, 800.f/600, 0.01f, 100.0f);
  Update();
}

void CameraDevice::CreateProjectionMatrix(float fov, float aspect, float nearPlane, float farPlane)
{
  m_fov = fov;
  m_aspect = aspect;
  m_near = nearPlane;
  m_far = farPlane;

  XMMATRIX projection = XMMatrixPerspectiveFovLH(fov, aspect, nearPlane, farPlane);
  XMStoreFloat4x4(&m_proj, projection);
}

void CameraDevice::MoveForward(float units)
{
  XMVECTOR velocity = XMLoadFloat3(&m_velocity);
  XMVECTOR dir = XMLoadFloat3(&m_direction);

  XMStoreFloat3(&m_velocity, velocity + dir * units);
}

void CameraDevice::Strafe(float units)
{
  XMVECTOR velocity = XMLoadFloat3(&m_velocity);
  XMVECTOR strafe = XMLoadFloat3(&m_strafe);

  XMStoreFloat3(&m_velocity, velocity + strafe * units);
}

void CameraDevice::MoveUp(float units)
{
  m_velocity.y += units;
}

void CameraDevice::SetYaw(float units)
{
  if(units != 0.0f)
  {
    XMMATRIX rot = XMMatrixRotationAxis(XMLoadFloat3(&m_up), units);
    XMVECTOR strafe =  XMVector3TransformNormal(XMLoadFloat3(&m_strafe), rot);
    XMVECTOR dir = XMVector3TransformNormal(XMLoadFloat3(&m_direction), rot);

    XMStoreFloat3(&m_strafe, strafe);
    XMStoreFloat3(&m_direction, dir);
  }
}

void CameraDevice::SetPitch(float units)
{
  if(units != 0.0f)
  {
    m_pitch -= units;

    XMMATRIX rot = XMMatrixRotationAxis(XMLoadFloat3(&m_strafe), units);
    XMVECTOR up =  XMVector3TransformNormal(XMLoadFloat3(&m_up), rot);
    XMVECTOR dir = XMVector3TransformNormal(XMLoadFloat3(&m_direction), rot);

    XMStoreFloat3(&m_up, up);
    XMStoreFloat3(&m_direction, dir);
  }
}

void CameraDevice::SetRoll(float units)
{
  if(units != 0.0f)
  {
    XMMATRIX rot = XMMatrixRotationAxis(XMLoadFloat3(&m_target), units);
    XMVECTOR strafe = XMVector3TransformNormal(XMLoadFloat3(&m_strafe), rot);
    XMVECTOR up = XMVector3TransformNormal(XMLoadFloat3(&m_up), rot);

    XMStoreFloat3(&m_strafe, strafe);
    XMStoreFloat3(&m_up, up);
  }
}

void CameraDevice::Update()
{
  XMVECTOR pos = XMLoadFloat3(&m_pos);
  XMVECTOR target = XMLoadFloat3(&m_target);
  XMVECTOR vel = XMLoadFloat3(&m_velocity);
  XMVECTOR dir = XMLoadFloat3(&m_direction);

  XMFLOAT3 tmp = XMFLOAT3(0, 1, 0);

  XMVECTOR up = XMLoadFloat3(&tmp);

  pos += vel;
  vel = XMVECTOR();
  target = pos + dir;

  XMMATRIX view = XMMatrixLookAtLH(pos, target, up);
  XMStoreFloat4x4(&m_view, view);

  m_strafe = XMFLOAT3(m_view._11, m_view._21, m_view._31);
  m_up = XMFLOAT3(m_view._12, m_view._22, m_view._32);
  m_direction = XMFLOAT3(m_view._13, m_view._23, m_view._33);

  float dirLenXZ = sqrtf(m_direction.z * m_direction.z + m_direction.x * m_direction.x);
  m_pitch = atan2f(m_direction.y, dirLenXZ);
  m_yaw = atan2f(m_direction.x, m_direction.z);

  XMStoreFloat3(&m_pos, pos);
  XMStoreFloat3(&m_target, target);
  XMStoreFloat3(&m_velocity, vel);
  XMStoreFloat3(&m_direction, dir);
}

void CameraDevice::SetPosition(XMFLOAT3 pos)
{
  m_pos = pos;
}

void CameraDevice::SetLookAt(XMFLOAT3 target)
{
  m_target = target;
  XMVECTOR dir = (XMLoadFloat3(&m_target) - XMLoadFloat3(&m_pos));
  XMStoreFloat3(&m_direction, dir);
}

XMMATRIX CameraDevice::GetProjection()
{
  return XMLoadFloat4x4(&m_proj);
}

XMMATRIX CameraDevice::GetView()
{
  return XMLoadFloat4x4(&m_view);
}

void CameraDevice::SetAspect(float aspect)
{
  CreateProjectionMatrix(m_fov, aspect, m_near, m_far);
}

void CameraDevice::SetFOV(float fov)
{
  CreateProjectionMatrix(fov, m_aspect, m_near, m_far);
}

void CameraDevice::SetNear(float nearZ)
{
  CreateProjectionMatrix(m_fov, m_aspect, nearZ, m_far);
}

void CameraDevice::SetFar(float farZ)
{
  CreateProjectionMatrix(m_fov, m_aspect, m_near, farZ);
}

XMVECTOR CameraDevice::GetPosition()
{
  return XMLoadFloat3(&m_pos);
}

XMVECTOR CameraDevice::GetLookAt()
{
  return XMLoadFloat3(&m_target);
}

float CameraDevice::GetFOV()
{
  return m_fov;
}

float CameraDevice::GetAspect()
{
  return m_aspect;
}

float CameraDevice::GetNear()
{
  return m_near;
}

float CameraDevice::GetFar()
{
  return m_far;
}

float CameraDevice::GetPitch()
{
  return m_pitch;
}

float CameraDevice::GetRoll()
{
  return m_roll;
}

float CameraDevice::GetYaw()
{
  return m_yaw;
}